/*
# Copyright (c) 2024 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*/
#include "i420_render.h"
#include "egl_render_context.h"
#include "aki/jsbind.h"
#include <cstdint>
std::string I420Render::vertexShader_ = R"delimiter(
attribute vec3 position;
attribute vec2 texCoord;

varying vec2 vTexCoord;

uniform mat4 matTransform;

void main()
{
    gl_Position = matTransform * vec4(position, 1.0);
    vTexCoord = texCoord;
}
)delimiter";

std::string I420Render::fragmentShader_ = R"delimiter(
precision highp float;
varying vec2 vTexCoord;
uniform sampler2D tex_y;
uniform sampler2D tex_u;
uniform sampler2D tex_v;

vec4 sample(vec2 p) {
    float y = texture2D(tex_y, p).r * 1.16438;
    float u = texture2D(tex_u, p).r;
    float v = texture2D(tex_v, p).r;
    return vec4(y + 1.59603 * v - 0.874202,
    y - 0.391762 * u - 0.812968 * v + 0.531668,
    y + 2.01723 * u - 1.08563, 1.0);
}

void main()
{
    gl_FragColor = sample(vTexCoord);
}
)delimiter";

GLfloat I420Render::vertices_[] = {
        // positions        // texture coords
         1.0f,  1.0f, 0.0f,  1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,  1.0f, 1.0f,
        -1.0f, -1.0f, 0.0f,  0.0f, 1.0f,
        -1.0f,  1.0f, 0.0f,  0.0f, 0.0f
};
GLuint I420Render::indices_[] = {
    0, 1, 3, // first triangle
    1, 2, 3  // second triangle
};

I420Render::~I420Render()
{
    eglRenderContext_.MakeCurrent(eglSurface_);
    glDeleteVertexArrays(1, &vAO_);
    glDeleteBuffers(1, &vBO_);
    glDeleteTextures(3, textures_);
    shaderProgram_->Destroy();
    eglRenderContext_.MakeCurrent();
}

int32_t I420Render::Init(int32_t width, int32_t height, OHNativeWindow *nativeWindow)
{
    AKI_LOG(INFO) << "I420Render::Init" << width << "  " << height;
    int32_t ret = 0;
    width_ = width;
    height_ = height;

    if (!nativeWindow) {
        AKI_LOG(ERROR) <<"OHNativeWindow NULL";
        ret = -1;
        return ret;
    }
    nativeWindow_ = nativeWindow;
    if (!eglRenderContext_.Init()) {
        AKI_LOG(ERROR) <<"EGL init ERROR";
        ret = -1;
        return ret;
    }
    eglSurface_ = eglRenderContext_.CreateEglSurface(reinterpret_cast<EGLNativeWindowType>(nativeWindow_));
    if (EGL_NO_SURFACE == eglSurface_) {
        AKI_LOG(ERROR) <<"EGL surface create ERROR";
        ret = -1;
        return ret;
    }
    if (0 != PrepareShader()) {
        AKI_LOG(ERROR) <<"EGL make current ERROR";
        ret = -1;
        return ret;
    }

    if (0 != GenGLResource()) {
        AKI_LOG(ERROR) <<"GenGLResource ERROR";
        ret = -1;
        return ret;
    }
    return ret;
}

int32_t I420Render::PrepareShader()
{
    int32_t ret = 0;
    if (eglRenderContext_.MakeCurrent(eglSurface_)) {
        AKI_LOG(ERROR) <<"EGL make current ERROR";
        ret = -1;
        return ret;
    }
    // compile/use/set shader program
    shaderProgram_ = std::make_unique<ShaderProgram>(I420Render::vertexShader_, I420Render::fragmentShader_);
    if (!shaderProgram_) {
        AKI_LOG(ERROR) <<"shaderProgram ERROR";
        ret = -1;
        return ret;
    }
    if (shaderProgram_->Compile()) {
        AKI_LOG(ERROR) <<"shaderProgram compile ERROR";
        ret = -1;
        return ret;
    }
    shaderProgram_->Use();
    shaderProgram_->SetInt("tex_y", 0);
    shaderProgram_->SetInt("tex_u", 1);
    shaderProgram_->SetInt("tex_v", 2);

    eglRenderContext_.MakeCurrent();
    return ret;
}

int32_t I420Render::GenGLResource()
{
    int32_t ret = 0;
    if (eglRenderContext_.MakeCurrent(eglSurface_)) {
        AKI_LOG(ERROR) <<"EGL make current ERROR";
        ret = -1;
        return ret;
    }
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glViewport(0, 0, width_, height_);
    // Gen vao,vbo
    glGenVertexArrays(1, &vAO_);
    glGenBuffers(1, &vBO_);
    glBindVertexArray(vAO_);
    glBindBuffer(GL_ARRAY_BUFFER, vBO_);
    glBufferData(GL_ARRAY_BUFFER, sizeof(I420Render::vertices_), I420Render::vertices_, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // Gen tex
    glGenTextures(3, textures_);
    for (int i = 0; i < 3; i++) {
        glActiveTexture(GL_TEXTURE0 + i);
        glBindTexture(GL_TEXTURE_2D, textures_[i]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        if (!glIsTexture(textures_[i])) {
            AKI_LOG(ERROR) <<"Gen Texture ERROR";
            ret = -2;
            return ret;
        }
    }
    eglRenderContext_.MakeCurrent();
    return 0;
}

int32_t I420Render::Render(uint8_t *yData, uint8_t *uData, uint8_t *vData, int32_t frameWidth, int32_t frameHeight)
{
    // TODO ֡帧宽高，window宽高，做屏幕适配，计算矩阵变换 默认矩阵不变换
    float matrix[16] {1.0,  0.0, 0.0, 0.0,
                      0.0,  1.0, 0.0, 0.0,
                      0.0,  0.0, 1.0, 0.0,
                      0.0,  0.0, 0.0, 1.0};

    int32_t ret = eglRenderContext_.MakeCurrent(eglSurface_);
    if (ret) {
        AKI_LOG(ERROR) << "MakeCurrent ERROR";
    }
    shaderProgram_->SetMatrix4v("matTransform", matrix, 16);
    glClear(GL_COLOR_BUFFER_BIT);

    // update I420 Y data
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textures_[0]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frameWidth, frameHeight, 0, GL_RED, GL_UNSIGNED_BYTE, yData);
    // update I420 U data
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, textures_[1]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frameWidth / 2, frameHeight / 2, 0, GL_RED, GL_UNSIGNED_BYTE, uData);
    // update I420 V data
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, textures_[2]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frameWidth / 2, frameHeight / 2, 0, GL_RED, GL_UNSIGNED_BYTE, vData);
    // Draw
    glBindVertexArray(vAO_);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, I420Render::indices_);

    eglRenderContext_.SwapBuffers(eglSurface_);
    ret = eglRenderContext_.MakeCurrent();
    if (ret) {
        AKI_LOG(ERROR) << "MakeCurrent ERROR";
    }
    return 0;
}
